include Java

import org.jdesktop.jdic.desktop.Desktop
import org.jdesktop.jdic.tray.TrayIcon
import org.jdesktop.jdic.tray.SystemTray
import javax.swing.JPopupMenu
import javax.swing.JMenuItem
import javax.swing.ImageIcon
import javax.swing.JFrame
import javax.swing.JTextField
import javax.swing.BorderFactory
import javax.swing.JButton
import javax.swing.JTextArea
import javax.swing.JScrollPane
import javax.swing.JPanel
import javax.swing.JOptionPane

import java.awt.BorderLayout
import java.awt.Font
import java.awt.Color
import java.awt.GraphicsEnvironment
import java.awt.event.ActionListener

import java.net.URL
import java.lang.System

import com.jedit.syntex.JEditTextArea

$LOAD_PATH << File.dirname(__FILE__)

require 'logger'
require "pstore"

def logger
  @logger ||= Logger.new('runtime.log', 10, 1024000)
end

require 'cc_monitor'

BASE_DIR = File.expand_path(File.dirname(__FILE__))
ICONS = {
  :'loaded error' => ImageIcon.new("#{BASE_DIR}/images/loaded error-spinner.gif"),
  :succeeded => ImageIcon.new("#{BASE_DIR}/images/succeeded-spinner.gif"),
  :failed => ImageIcon.new("#{BASE_DIR}/images/failed-spinner.gif"),
  :notice => ImageIcon.new("#{BASE_DIR}/images/dance.gif")
}
ICONS[:red] = ICONS[:failed]
ICONS[:default] = ICONS[:'loaded error']


ICON = ICONS[:default]
ICON_IMAGE = ImageIcon.new("#{BASE_DIR}/images/succeeded.png").getImage

class QuitAction
  include ActionListener
  def actionPerformed(evt)
    System.exit(0);
  end
end

class OpenMonitorAction
  include ActionListener

  def initialize(frame)
    @frame = frame
    @frame.show
  end

  def actionPerformed(evt)
    @frame.show
  end
end

class LoadMonitorAction
  include ActionListener

  def initialize(frame)
    @frame = frame
  end

  def actionPerformed(evt)
    begin
      CcMonitor.request(@frame.uri_editor.get_text) do |content|
        @frame.monitor_editor.set_text content
      end
    rescue Exception => e
      logger.error { e }
      JOptionPane.showMessageDialog(@frame.uri_editor, e.message, "Can't Load Monitor", JOptionPane::ERROR_MESSAGE)
      @frame.uri_editor.requestFocus
      @frame.uri_editor.select_all
    end
  end
end

class StoreConfigurationAction
  include ActionListener

  LOCAL_CCM_FILE_NAME = 'local.ccm'
  CONFIGURATION_FILE = 'ccr.config.pstore'

  def initialize(frame)
    @frame = frame
    load_configuration
  end

  def actionPerformed(evt)
    @frame.dispose
    store_local_ccm
    store_configuration
    CcMonitor::Service.new.set_uri parse(evt.getActionCommand) 
  end

  def parse(command)
    'local' == command ? LOCAL_CCM_FILE_NAME : remote_uri
  end

  def store_local_ccm
    File.open(LOCAL_CCM_FILE_NAME, 'w') do |file|
      file.syswrite(local_ccm)
    end if !local_ccm.nil? && local_ccm.length > 0
  end

  def store_configuration
    config = PStore.new(CONFIGURATION_FILE)
    config.transaction do
      config[:remote_uri] = remote_uri
      config[:local_ccm] = local_ccm
    end
  end

  def load_configuration
    config = PStore.new(CONFIGURATION_FILE)
    config.transaction(true) do
      @frame.uri_editor.set_text config[:remote_uri]
      @frame.monitor_editor.set_text config[:local_ccm]
    end
  end

  def remote_uri
    @frame.uri_editor.get_text
  end

  def local_ccm
    @frame.monitor_editor.get_text
  end
end

class Runner
  include ActionListener

  def actionPerformed(evt)
    if CcMonitor::Service.new.running?
      CcMonitor::Service.new.stop
      @stop_menu.setEnabled false
      @start_menu.setEnabled true
    else
      Thread.new do
        CcMonitor::Service.new.start
      end
      @stop_menu.setEnabled true
      @start_menu.setEnabled false
    end
  end

  def start_menu(menu)
    @start_menu = menu
  end

  def stop_menu(menu)
    @stop_menu = menu
  end
end

class MonitorFrame

  attr_reader :uri_editor, :monitor_editor

  def initialize
    @frame = JFrame.new

    uri_pane = JPanel.new BorderLayout.new
    uri_pane.add new_uri_editor, BorderLayout::CENTER
    load_button = JButton.new("Load")
    load_button.addActionListener LoadMonitorAction.new(self)
    uri_pane.add load_button, BorderLayout::EAST
    uri_pane.setBorder new_border("monitor uri")

    monitor_pane = JPanel.new BorderLayout.new
    monitor_pane.add new_monitor_editor, BorderLayout::CENTER
    monitor_pane.setBorder new_border("local monitor")

    action_bar = JPanel.new

    store_action = StoreConfigurationAction.new self
    run_local = JButton.new('Store & Run Local CCM')
    run_local.set_action_command 'local'
    run_local.addActionListener store_action
    run_remote = JButton.new('Store & Run Remote CCM')
    run_remote.set_action_command 'remote'
    run_remote.addActionListener store_action

    action_bar.add run_local
    action_bar.add run_remote

    pane = JPanel.new(BorderLayout.new)
    pane.add uri_pane, BorderLayout::NORTH
    pane.add monitor_pane, BorderLayout::CENTER
    pane.add action_bar, BorderLayout::SOUTH

    @frame.get_content_pane.add pane
    @frame.setTitle 'CC Monitor'
    @frame.setIconImage ICON_IMAGE
    @frame.pack
    @frame.setSize 800, 600
  end

  def show
    bounds = GraphicsEnvironment.getLocalGraphicsEnvironment.getMaximumWindowBounds
    centerX = bounds.getCenterX
    centerY = bounds.getCenterY
    @frame.setLocation(centerX - @frame.getWidth / 2, centerY - @frame.getHeight / 2);

    @frame.show
    @uri_editor.requestFocus
  end

  def dispose
    @frame.dispose
  end

  private
  def new_monitor_editor
    @monitor_editor = JEditTextArea.new
    @monitor_editor.setTokenMarker com.jedit.syntex.RubyTokenMarker.new
    init_comp(@monitor_editor)
  end

  def new_uri_editor
    @uri_editor = init_comp JTextField.new
  end

  def init_comp(comp)
    comp.setFont Font.new("Verdana", Font::PLAIN, 12)
    comp.setBackground Color::BLACK
    comp.setForeground Color::WHITE
    comp.setCaretColor Color::RED
    comp
  end

  def new_border(title)
    border = BorderFactory.createTitledBorder title
    border.setTitleFont Font.new("Times New Roman", Font::BOLD, 20)
    border.setTitleColor Color::BLUE
    border
  end
end

def openLink(link)
  Thread.new do
    begin
      Desktop.browse URL.new(link)
    rescue Exception => e
      logger.error { e }
      JOptionPane.showMessageDialog(nil, e.message, "Could not open system browser", JOptionPane::ERROR_MESSAGE)
    end
  end
end

class OpenLinkActionListener
  include ActionListener
  
  def initialize(link)
    @link = link
  end

  def actionPerformed(evt)
    openLink(@link)
  end
end

class OutputConverter

  def initialize(menu)
    @menu = menu
    @build_menus = []
  end

  def convert(output)
    return if output.nil? || !output.kind_of?(Hash) || output.empty?
    return if same_with_pre?(output)
    clear_build_menus
    output.each do |key, value|
      self.send("convert_#{key}", value)
    end
  end

  def same_with_pre?(output)
    same = if defined?(@pre)
      @pre.to_yaml == output.to_yaml      
    end
    @pre = output
    same
  end

  def convert_background(bg)
    TRAY_ICON.set_icon ICONS[bg]
  end

  def convert_links(links)
    links.each do |link|
      item = JMenuItem.new(link[:message], ICONS[:notice])
      item.addActionListener OpenLinkActionListener.new(link[:url])
      add item
    end
  end

  def convert_icons(icons)
    icons.each do |icon|
      item = JMenuItem.new icon[:description], ICONS[icon[:status].to_sym]
      item.addActionListener OpenLinkActionListener.new(icon[:link])
      add item
    end
  end

  def convert_errors(errors)
    errors.each do |error|
      item = JMenuItem.new error[:message], ICONS[:notice]
      add item
    end
  end

  def add(menu_item)
    @build_menus << menu_item
    @menu.add menu_item
  end

  def clear_build_menus
    @build_menus.each do |item|
      @menu.remove item
    end
    @build_menus.clear
  end
end
runner = Runner.new

menu = JPopupMenu.new("Menu")
menuItem = JMenuItem.new("Open Monitor")
menuItem.addActionListener OpenMonitorAction.new(MonitorFrame.new)
menu.add menuItem

menuItem = JMenuItem.new("start")
menuItem.addActionListener runner
runner.start_menu menuItem
menu.add menuItem

menuItem = JMenuItem.new("stop")
menuItem.addActionListener runner
runner.stop_menu menuItem
menu.add menuItem

menu.addSeparator
menuItem = JMenuItem.new("Quit")
menuItem.addActionListener QuitAction.new
menu.add menuItem

TRAY_ICON = TrayIcon.new(ICON, "CC Monitor Tray Icon", menu);
TRAY_ICON.setIconAutoSize true

SystemTray.getDefaultSystemTray.addTrayIcon TRAY_ICON

runner.actionPerformed nil

Thread.new do
  @converter = OutputConverter.new menu
  loop do
    @converter.convert CcMonitor::Service.new.output
    sleep(10)
  end
end

